#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "main.h"
#include "webglk/glk.h"
#include <time.h>
#include <unistd.h>

static void decodePost(char *,long);
static void setArg(char *,char *,long);

char *gamename=NULL,*user=NULL,*input=NULL,*mode=NULL;
char *imagename,*romname;
winid_t window=0;
uint32_t screenwidth=0,screenheight=0;
uint32_t font3width=0,font3height=0;
uint32_t font4width=0,font4height=0;

Variables g;
uint8_t *rom;
uint8_t *stack;
int done=0;

int main()
{
	FILE *f;
	char *postlens;
	long postlen,i;
	uint32_t len;

	webglk_open();

	postlens=getenv("CONTENT_LENGTH");
	if (postlens!=NULL && sscanf(postlens,"%ld",&postlen)==1)
	{
		char *post=malloc(postlen);
		fgets(post,postlen,stdin);
		decodePost(post,postlen);
		free(post);
	}
	else
		fatal("You can't run this by itself");

	if (gamename==NULL || user==NULL) fatal("Missing game name or user");

	/* since gamename and user are going to be used in filenames,
		lets make sure that they can only be A-Za-z0-9._  */
	for (i=0;i<strlen(gamename);i++)
		if ((gamename[i]<'A' || gamename[i]>'Z') &&
		    (gamename[i]<'a' || gamename[i]>'z') &&
		    (gamename[i]<'0' || gamename[i]>'9') &&
		    gamename[i]!='.' && gamename[i]!='_')
		{
			gamename[i]=0;
			break;
		}
	for (i=0;i<strlen(user);i++)
		if ((user[i]<'A' || user[i]>'Z') &&
		    (user[i]<'a' || user[i]>'z') &&
		    (user[i]<'0' || user[i]>'9') &&
		    user[i]!='.' && user[i]!='_')
		{
			user[i]=0;
			break;
		}

	len=20+strlen(gamename)+1+strlen(user);
	imagename=malloc(len);
	snprintf(imagename,len,"users/%s_%s",user,gamename);

	len=20+strlen(gamename);
	romname=malloc(len);
	snprintf(romname,len,"roms/%s",gamename);

	if (mode && !strcmp(mode,"cleanup"))
	{
		unlink(imagename);
		exit(0);
	}

	f=fopen(imagename,"rb");
	if (mode && !strcmp(mode,"start"))
	{
		if (f) fclose(f);
		f=NULL;
	}
	if (f==NULL)
	{
		g.randa=time(0);
		g.randi=0;
		loadROM(1);
		if (g.type==1)
		{
			z_init();
			z_run();
		}
		else
		{
			g_init();
			g_run();
		}
	}
	else
	{
		fread(&g,sizeof(g),1,f);
		rom=malloc(g.romSize);
		fread(rom,g.romSize,1,f);
		stack=malloc(g.stackSize);
		fread(stack,g.stackSize,1,f);
		webglk_load(f);
		fclose(f);
		if (g.type==1)
		{
			event_t event;
			if (mode && !strcmp(mode,"char"))
			{
				event.type=evtype_CharInput;
				event.win=window;
				event.val1=input[0];
			}
			else if (mode && !strcmp(mode,"line"))
			{
				int i,ofs;
				event.type=evtype_LineInput;
				event.win=window;
				event.val1=strlen(input);
				ofs=g.inputbuffer+(g.version<5?1:2);
				for (i=0;i<event.val1;i++)
				{
					if (input[i]>='A' && input[i]<='Z')
						input[i]+=0x20;
					rom[ofs+i]=input[i];
				}
				if (g.version<5) rom[g.inputbuffer+1+event.val1]=0;
				else rom[g.inputbuffer+1]=event.val1;
			}
			else if (mode && !strcmp(mode,"timeout"))
			{
				event.type=evtype_Timer;
			}
			else fatal("handle mode %s",mode);
			z_select(&event);
			z_run();
		}
		else
		{
			event_t event;
			if (mode && !strcmp(mode,"char"))
			{
				event.type=evtype_CharInput;
				event.win=window;
				event.val1=input[0];
			}
			else if (mode && !strcmp(mode,"line"))
			{
				int i;
				event.type=evtype_LineInput;
				event.win=window;
				event.val1=strlen(input);
				for (i=0;i<event.val1;i++)
					rom[g.inputbuffer+i]=input[i];
			}
			else if (mode && !strcmp(mode,"hyper"))
			{
				event.type=evtype_Hyperlink;
				event.win=window;
				sscanf(input,"%u",&event.val1);
			}
			else if (mode && !strcmp(mode,"timeout"))
			{
				event.type=evtype_Timer;
			}
			else fatal("handle mode %s",mode);
			g_select(&event);
			g_run();
		}
	}
	if (done)
	{
		printf("{m:'quit'},\n");
		unlink(imagename);
	}
	else
	{
		f=fopen(imagename,"wb");
		if (f==NULL) fatal("Can't create game instance");
		fwrite(&g,sizeof(g),1,f);
		fwrite(rom,g.romSize,1,f);
		fwrite(stack,g.stackSize,1,f);
		webglk_save(f);
		fclose(f);
	}
	webglk_close();
	return 0;
}

uint8_t save(uint32_t start,uint32_t length,uint32_t resultmode,uint32_t resultvalue)
{
	FILE *f;
	char *savename;
	int len;

	len=20+strlen(gamename)+1+strlen(user);
	savename=malloc(len);
	snprintf(savename,len,"users/%s_%s.sav",user,gamename);
	f=fopen(savename,"wb");
	free(savename);
	if (!f) return 0;

	fwrite(rom+start,length,1,f);
	fwrite(stack,g.stackSize,1,f);
	fwrite(&g.pc,4,1,f);
	fwrite(&g.fp,4,1,f);
	fwrite(&g.sp,4,1,f);
	fwrite(&g.lp,4,1,f);
	fwrite(&resultmode,4,1,f);
	fwrite(&resultvalue,4,1,f);
	fclose(f);
	return 1;
}
uint8_t load(uint32_t start,uint32_t length,uint32_t *resultmode,uint32_t *resultvalue)
{
	FILE *f;
	char *savename;
	int len;

	len=20+strlen(gamename)+1+strlen(user);
	savename=malloc(len);
	snprintf(savename,len,"users/%s_%s.sav",user,gamename);
	f=fopen(savename,"rb");
	free(savename);
	if (!f) return 0;

	fread(rom+start,length,1,f);
	fread(stack,g.stackSize,1,f);
	fread(&g.pc,4,1,f);
	fread(&g.fp,4,1,f);
	fread(&g.sp,4,1,f);
	fread(&g.lp,4,1,f);
	fread(resultmode,4,1,f);
	fread(resultvalue,4,1,f);
	fclose(f);
	return 1;
}

static void decodePost(char *post,long postlen)
{
	char *key;
	long i,value;
	int readval=0;
	key=post;
	for (i=0;i<postlen;i++)
	{
		if (post[i]=='=')
		{
			post[i]=0;
			readval=1;
			value=i+1;
		}
		if (post[i]=='&')
		{
			setArg(key,post+value,i-value);
			key=post+i+1;
			readval=0;
		}
	}
	if (readval) setArg(key,post+value,i-value);
}
static void setArg(char *key,char *evalue,long evaluelen)
{
	long i;
	char *value=malloc(evaluelen+1),*p;
	p=value;
	for (i=0;i<evaluelen;i++)
	{
		if (evalue[i]=='+') *p++=' ';
		else if (evalue[i]=='%')
		{
			int code;
			if (sscanf(evalue+i+1,"%2x",&code)!=1) code='?';
			*p++=code;
			i+=2;
		} else *p++=evalue[i];
	}
	*p++=0;
	if (!strcmp(key,"game")) gamename=strdup(value);
	else if (!strcmp(key,"user")) user=strdup(value);
	else if (!strcmp(key,"input")) input=strdup(value);
	else if (!strcmp(key,"mode")) mode=strdup(value);
	else if (!strcmp(key,"win")) sscanf(value,"%u",&window);
	else if (!strcmp(key,"width")) sscanf(value,"%u",&screenwidth);
	else if (!strcmp(key,"height")) sscanf(value,"%u",&screenheight);
	else if (!strcmp(key,"width3")) sscanf(value,"%u",&font3width);
	else if (!strcmp(key,"height3")) sscanf(value,"%u",&font3height);
	else if (!strcmp(key,"width4")) sscanf(value,"%u",&font4width);
	else if (!strcmp(key,"height4")) sscanf(value,"%u",&font4height);
	free(value);
}

#define r32(x) ((buf[x]<<24)|(buf[x+1]<<16)|(buf[x+2]<<8)|buf[x+3])
void loadROM(uint8_t init)
{
	uint32_t len,offs=0;
	uint8_t buf[12];
	FILE *f;

	f=fopen(romname,"rb");
	if (f==NULL) fatal("Couldn't open game: %s",gamename);

	fread(buf,4,1,f);
	if (r32(0)==0x464f524d) //blorb (FORM)
	{
		uint32_t numres,i;

		fseek(f,12,SEEK_SET);
		fread(buf,8,1,f);
		if (r32(0)!=0x52496478)	//RIdx
			fatal("Invalid Blorb format");
		fread(buf,4,1,f);
		numres=r32(0);
		for (i=0;i<numres;i++)
		{
			fread(buf,12,1,f);
			if (r32(0)==0x45786563) //Exec
			{
				fseek(f,r32(8),SEEK_SET);
				break;
			}
		}
		fread(buf,8,1,f);
		if (r32(0)==0x5a434f44) //ZCOD
			g.type=1;
		else if (r32(0)==0x474c554c) //GLUL
			g.type=2;
		len=r32(4);
		if (!init)
		{
			if (g.type==1) len=read16(0xe);
			else
			{
				offs=read32(8);
				len=read32(0x10)-offs;
			}
		}
	}
	else if (r32(0)==0x476c756c) //Glul
	{
		g.type=2;
		if (!init)
		{
			offs=read32(8);
			len=read32(0x10)-offs;
		}
		else
		{
			fseek(f,0,SEEK_END);
			len=ftell(f);
		}
		fseek(f,0,SEEK_SET);
	}
	else
	{
		g.type=1;
		if (!init) len=read16(14);
		else
		{
			fseek(f,0,SEEK_END);
			len=ftell(f);
		}
		fseek(f,0,SEEK_SET);
	}
	if (init)
	{
		rom=malloc(len);
		g.romSize=len;
	}
	fseek(f,offs,SEEK_CUR);
	fread(rom+offs,len,1,f);
	fclose(f);
}
void write16(uint32_t x,uint16_t v)
{
	rom[x++]=v>>8;
	rom[x++]=v&0xff;
}
void write32(uint32_t x,uint32_t v)
{
	rom[x++]=v>>24;
	rom[x++]=(v>>16)&0xff;
	rom[x++]=(v>>8)&0xff;
	rom[x++]=v&0xff;
}
void push(uint32_t v)
{
	stack[g.sp++]=v>>24;
	stack[g.sp++]=(v>>16)&0xff;
	stack[g.sp++]=(v>>8)&0xff;
	stack[g.sp++]=v&0xff;
}
uint32_t pop()
{
	uint32_t r=0;
	r|=stack[--g.sp];
	r|=stack[--g.sp]<<8;
	r|=stack[--g.sp]<<16;
	r|=stack[--g.sp]<<24;
	return r;
}
void poke(uint32_t x,uint32_t v)
{
	stack[x++]=v>>24;
	stack[x++]=(v>>16)&0xff;
	stack[x++]=(v>>8)&0xff;
	stack[x++]=v&0xff;
}
void printNum(int32_t n)
{
	uint32_t max=1;
	if (n<0)
	{
		if (g.iosys==2) glk_put_char('-');
		else if (g.iosys!=0) fatal("Invalid printnum iosys: %d",g.iosys);
		n=-n;
	}
	while (max*10<=n) max*=10;
	while (max>0)
	{
		if (g.iosys==2) glk_put_char(((n/max)%10)+'0');
		else if (g.iosys!=0) fatal("Invalid printnum iosys: %d",g.iosys);
		max/=10;
	}
}
